#include "stdio.h"
#include "stdlib.h"
#include "scheduler.h"
#include "schedulerasm.h"
#include "libasm.h"
#include "console.h"
#include "command.h"
#include "kernel.h"
#include "shell.h"
#include "string.h"
#include "defs.h"

static taskT multitasker[TASKS_COUNT];
static void halt(char *s);
static unsigned int get_previous_task_pid(int pid);
static taskT *get_next_task(int check_seconds);
static void free_child_tasks(unsigned int ppid, unsigned int pid);

extern unsigned long * page_directory;
extern unsigned long * page_table1;
extern unsigned long * page_table2;
extern unsigned long * page_table3;
int task_alloc(void);

int scheduler_priority = 0;
int last100[100]={0};
static int next_100_pid=0;
static int current_pid = 0;

/*
 * Acerca de las transiciones de estado de los procesos:
 * stdio se encarga de disparar el pase de corriendo a bloqueado (cuando el
 * buffer de stdin est vacio) y de bloqueado a listo (cuando el usuario
 * ingresa "Enter").
 * scheduler se encarga de switchear entre listo y corriendo en el timer
 * tick.
 *
 * IMPORTANTE: t->next_pid NUNCA debe estar en 0 siempre debe ser uno mas
 * que un indice valido (de un elemento del arreglo multitasker).
 */
int prevpid = 0;
int
scheduler(void)
{
	taskT *t, *cur;

	/* Obetener un puntero al proceso en ejecucion */
	cur = &(multitasker[current_pid - 1]);

	/* Pasar el estado del proceso que estaba ejecutando a READY */
	if (cur->status == RUNNING)
		cur->status = READY;

	/* Buscar un proceso para ejecutar */
	t = get_next_task(1);

	/* Si el proceso en ejecucion es el mismo que se selecciono para
	 * switchear; salir */
	if (cur->pid == t->pid)
	{
		cur->status = RUNNING;
		return 1;
	}

	/* El proceso que get_next_task devolvio para que ejecutemos lo pasamos
	 * a estado RUNNING. Nota: get_next_task siempre retorna un proceso en
	 * estado READY */
	t->status = RUNNING;
	/* Asignar el nuevo proceso como proceso actual  */
	current_pid = t->pid;

	prevpid = cur->pid;
	/* Habilito las paginas del stack y heap del nuevo proceso */
	pageUp(t->pid);

	/* Avisarle al PIC que ya termina la interrupcion. De cualquier manera
	 * no nos van a interrumpir puesto que todava no se ejecuto sti. */
	eoi();
	/* Switchear al nuevo proceso  */
	context_switch(t->sp, &(cur->sp));

	pageDown(prevpid);

	/* El proceso que estaba ejecutando previo al context_switch continua
	 * su ejecucion a partir de este return.*/
	return 1;
}

static taskT *
get_next_task(int check_seconds)
{
	int i, first = 1, found = 0;
	taskT *rta = NULL, *cur;

	cur = &(multitasker[current_pid - 1]);
	if ( cur->rounds && cur->status == READY )
	{
		(cur->rounds)--;
		last100[next_100_pid] = cur->pid;
		next_100_pid = (next_100_pid+1)%100;

		/* Me fijo si el scheduling con prioridad esta habilitado o no */
		if(scheduler_priority)
			return cur;
	}

	else
		cur->rounds = cur->priority;

	// Ciclar hasta encontrar un proceso preparado para ejecutar
	for(i = multitasker[current_pid - 1].next_pid;
			(i != multitasker[current_pid - 1].next_pid) || first;
			i = multitasker[i - 1].next_pid)
	{
		first = 0;
		cur = &multitasker[i - 1];
		// Si el proceso esta listo para ejecutar devolverlo.
		if (cur->status == READY && !found)
		{
			found = 1;
			rta = &multitasker[i - 1];
		}
		else if (check_seconds && cur->status == SLEEPING)
		{
			// Chequear si ya se debe poner en READY
			if (cur->ticks == 1)
				cur->status = READY;
			// Antes de decrementar chequear que no se lo haya
			// configurado para que se duerma infinitamente
			else if (cur->ticks != 0)
				(cur->ticks)--;
		}
	}
	if (found)
	{
		last100[next_100_pid] = rta->pid;
		next_100_pid = (next_100_pid+1)%100;
		return rta;
	}
	// Nunca debera llegar a esta lnea pus simpre habr un proceso idle
	halt("Error fatal: no hay ningun proceso listo para ejecutar\n");
	return NULL;
}


void
set_status(statusT status)
{
	/* Asignar el estado solicitado */
	multitasker[current_pid - 1].status = status;

	/* Si el estado solicitado a cambiar es BLOCKED se debe conmutar de
	 * tarea  */
	if (status == BLOCKED)
		scheduler();
}



void
scancode_arrived(void)
{
	int current_tty, i, first = 1;

	/* Averiguar cual es la tty activa */

	current_tty = get_active_tty_num();

	for(i = current_pid; i != current_pid || first;
			i = multitasker[i - 1].next_pid)
	{
		first = 0;
		if (multitasker[i - 1].tty == current_tty &&
				!multitasker[ i - 1].is_bkg &&
				multitasker[i - 1].status == BLOCKED)
		{
			multitasker[i - 1].status = READY;
			return ;
		}
	}
}

int
initialize_multitasking(void)
{
	unsigned int i;

	/* inicializar variables globales estaticas */
	current_pid = 0;
	mem_xy_pos(mem_video, 2, 2) = 'B';
	mem_xy_attr(mem_video, 2, 2) = WHITE_TXT;
	/* inicializar el arreglo de procesos como vacio */
	for (i = 0; i < TASKS_COUNT; i++)
		multitasker[i].status = KILLED;

	return 1;
}

void
show_multitasker(void)
{
	int i, j;
	char estados[6] = {'K', 'X', 'R', 'B', 'W', 'S'};
	int pos = 80 * 24;
	int first_pid, first = 1;

	/* Liberamos espacio en la barra de abajo */
	for (i=0; i < 60; i++)
		mem_video[ ((80 * 24) + i) * 2] = ' ';

	/* Buscamos el primer proceso que no sea KILLED */
	for (i=0; i < TASKS_COUNT; i++)
	{
		if (multitasker[i].status != KILLED)
		{
			first_pid = i + 1;
			break;
		}
	}

	/* Iteramos por la lista de procesos */

	for (i=first_pid, j=0; (i != first_pid || first) && j < 13; i = multitasker[i-1].next_pid, j++)
	{
		first = 0;
		if(multitasker[i-1].pid > 9)
		{
			mem_video[2*pos] = multitasker[i-1].pid/10 + '0';
			pos++;
		}
		mem_video[2*pos] = multitasker[i-1].pid%10 + '0';
		pos++;
		mem_video[2*pos] = estados[multitasker[i-1].status];
		pos += 2;
	}
	pos = 80 *24 + 50;
	mem_video[2*pos++] = '(';
	mem_video[2*pos++] = 't';
	mem_video[2*pos++] = 't';
	mem_video[2*pos++] = 'y';
	mem_video[2*pos++] = get_active_tty_num() + '0';
	mem_video[2*pos++] = ')';
	return;
}

static void
setrseed(void)
{
	int year, month, day, hour, min, sec;

	gettime(&year, &month, &day, &hour, &min, &sec);
	srand(year + month + day + hour + min + sec);
}

void
start_init()
{
	/* Alocamos el espacio en la tabla de procesos.  Esta funcion
	 * ya le asigna el sp */
	task_alloc();

	/* Seteo la seed random para las prioridades */
	setrseed();

	/* Cargamos manualmente los datos del proceso */
	multitasker[0].status = RUNNING;
	multitasker[0].pid = 1;
	multitasker[0].priority = MIN_PRIORITY;
	multitasker[0].rounds = multitasker[0].priority;
	multitasker[0].ppid = 0;
	multitasker[0].tty = -1;
	multitasker[0].is_bkg = 1;
	multitasker[0].cmd = "init";
	multitasker[0].next_pid = 1;

	/* Establecemos el pid del proceso actual */
	current_pid = 1;

	/* Iniciamos el proceso init */
	start_init_asm(init, multitasker[0].sp);
}

unsigned int
new_task(programT prg, int argc, char *argv[], int is_bkg)
{
	taskT *t, *cur;
	int newpid;

	/* alocar memoria para el nuevo proceso
	 * (inicializa adems t->pid, t->ss, t->sp, t->heap, t->currheap) */
	if ((newpid = task_alloc()) == 0)
		return 0;

	/* Obtener un puntero al nuevo proceso */
	t = &(multitasker[newpid - 1]);

	/* inicializacin de miembros del taskT */
	t->pid = newpid;
	t->priority = rand()%MAX_PRIO + 1;
	t->rounds = t->priority;
	t->ppid = current_pid; /* si es cero, esta bien */
	t->status = READY;
	t->cmd = get_command_alias(prg);

	if(prg == shell)
		t->ppid = 1;

	if (prg == clock)
		t->tty = -1;
	else if (current_pid > 1 && prg != shell)
		t->tty = multitasker[t->ppid - 1].tty;
	else
		t->tty = get_active_tty_num();

	t->is_bkg = (is_bkg ? 1 : 0);

	/* si no hay procesos, considerar que el actual es el nuevo */
	if (current_pid == 0)
	{
		t->next_pid = t->pid;
		cur = t;
	}
	else
	{
		/* Enlazar el nuevo proceso con el que corresponda */
		cur = &(multitasker[current_pid - 1]);
		t->next_pid = cur->next_pid;
		cur->next_pid = t->pid;
	}

	pageUp(t->pid);
	/* Registrar la nueva tarea en el scheduler */
	register_task(prg, &(t->sp), argc, argv);
	pageDown(t->pid);
	/* La linea de ejecucion sigue por aca, es decir, no hay un
	 * context_switch. */
	return t->pid;
}


static void
halt(char * s)
{
	/* Bloquear para siempre las interrupciones  */
	_Cli();
        maskPIC1(0xFF);
        maskPIC2(0xFF);

	if (s != NULL)
		puts(s);

	while(1)
		;
}

static unsigned int
get_previous_task_pid(int pid)
{
	unsigned int i, first = 1;

	for (i = current_pid; i != current_pid || first;
		       i = multitasker[i - 1].next_pid)
	{
		first = 0;
		if (multitasker[i - 1].status != KILLED)
			if (multitasker[i - 1].next_pid == pid)
				return i;
	}
	halt("\nLista mal vinculada\n");
	return 0;
}

void
exit_task(int status)
{
	taskT *t, *prev, *new, *father;
	int previous_pid, i;

	/* validar que una tarea actual no esta muerta */
	if (current_pid == 0 || multitasker[current_pid - 1].status == KILLED)
	       return;

	/* Matar a lo procesos que me tengan como padre */
	free_child_tasks(current_pid, current_pid);

	/* Obtener un puntero al actual */
	t = &(multitasker[current_pid - 1]);

	/* Si no tenia padre era el unico proceso en ejecucion
	 * entonces termina el s.o. (siempre queda al menos init). */
	if (t->ppid == 0) /* si no quedan procesos, terminar */
		halt("Ahora puede apagar el equipo.\n");

	/* Si el estado del proceso padre del que acaba de terminar era
	 * WAIT_CHILD pasarlo a READY */
	father = &multitasker[t->ppid - 1];
	if (father->status == WAITCHILD && (father->waitpid == ANY_CHILD ||
				father->waitpid == t->pid))
	{
		father->status = READY;
		father->child_status = status;
	}

	/* Si hay un proceso foreground esperando que termine el proceso actual lo
	 * como seteo como ready */
	for (i = 0; i < 32;	i++)
	{
		if(!strcmp(multitasker[i].cmd, "fg") && multitasker[i].waitpid == current_pid
			&& multitasker[i].status == WAITCHILD)
		{
			multitasker[i].status = READY;
			multitasker[i].child_status = status;
		}
	}

	/* Buscar el anterior y actualizar la lista de procesos con next_pid */
	previous_pid = get_previous_task_pid(t->pid);
	if (previous_pid == 0) /* Nunca debera suceder */
		halt("Fatal error: scheduler list unlinked\n");


	/* Vincular como corresponda */
	prev = &multitasker[previous_pid - 1];
	prev->next_pid = t->next_pid;

	/* Matar al proceso actual */
	t->status = KILLED;

	/* Indicar que no le quedan mas rounds a este proceso */
	t->rounds = 0;

	new = get_next_task(0);
	current_pid = new->pid;
	if (t->status == READY) /* defensivo */
		t->status = RUNNING;

	pageUp(t->pid);
	pageUp(new->pid);

	exit_switch(new->sp, t->pid);
	/* Nunca ejecuta este return */
	halt("Debug: nunca se deberia leer esto");
	return;
}

int
free_task(unsigned int pid, int status)
{
	taskT *t, *prev;
	unsigned int previous_pid;
	int i;

	/* Pid 0 reservado (por ahora no se le asigna a ningun proceso) */
	if (pid == 0)
		return 0;

	if (pid > TASKS_COUNT || multitasker[pid - 1].status == KILLED)
		return 0;

	/* Matar a lo procesos que lo tengan como padre */
	free_child_tasks(pid, current_pid);

	/* Obtener un puntero a la tarea en cuestion */
	t = &multitasker[pid - 1];

	/* Si el proceso que se quiere matar estaba en ejecucion, es decir
	 * si me quiero matar a mi mismo, llamar a exit_task. */
	if (t->status == RUNNING)
	{
		//exit_task(status);
		//return 1;
		/* Se desaloja el proceso corriendo del procesador si hay que liberarlo
		 * esto se utiliza como un fix para el ctrl+c porque si no a veces volvia
		 * inestable al sistema
		 */
		yield();
	}

	/* Si se quiere matar al proceso init */
	if (t->ppid == 0)
	{
		t->status = KILLED;
		halt("Ahora puede apagar el equipo.\n");
	}

	/* Si el estado del proceso padre del que acaba de terminar era
	 * WAIT_CHILD pasarlo a READY */
	if (multitasker[t->ppid - 1].status == WAITCHILD && multitasker[t->ppid - 1].waitpid == t->pid)
	{
		multitasker[t->ppid - 1].status = READY;
		multitasker[t->ppid - 1].child_status = status;
	}

	/* Si hay algun proceso esperando que no sea el padre lo seteo como
	 * ready */
	for (i = 0; i < TASKS_COUNT; i++)
	{
		if(!strcmp(multitasker[i].cmd, "fg") && multitasker[i].waitpid == t->pid
			&& multitasker[i].status == WAITCHILD)
		{
			multitasker[i].status = READY;
			multitasker[i].child_status = status;
		}
	}
	/* Buscar el anterior y actualizar la lista de procesos con next_pid */
	previous_pid = get_previous_task_pid(t->pid);
	if (previous_pid == 0) /* Nunca debera suceder */
		halt("Fatal error: scheduler list unlinked\n");

	/* Vincular como corresponda */
	prev = &multitasker[previous_pid - 1];
	prev->next_pid = t->next_pid;

	t->status = KILLED;

	return 1;
}

taskT *
get_task_by_pid(int pid)
{
	return &(multitasker[pid - 1]);
}

int
get_next_task_pid(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].next_pid);
}

int
get_task_pid(taskT *t)
{
	return ((t == NULL) ? current_pid : t->pid);
}

int
get_task_ppid(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].ppid);
}

int
get_task_prio(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].priority);
}

int
get_task_child_status()
{
	return multitasker[current_pid - 1].child_status;
}

void
set_task_waitpid(unsigned int pid)
{
	multitasker[current_pid - 1].waitpid = pid;
}

void
set_task_ticks(unsigned int ticks)
{
	multitasker[current_pid - 1].ticks = ticks;
}


statusT
get_task_status(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].status);
}

int
is_bkg_task(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].is_bkg);
}

int
get_task_tty(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].tty);
}

char *
get_task_cmd(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].cmd);
}

int
get_task_currheap(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].currheap);
}

int
get_task_heap(int pid)
{
	if (pid == 0)
		pid = current_pid;

	return ((pid == 0) ? 0 : multitasker[pid - 1].heap);
}

void *
incCurrHeap(int size, int pid)
{
	void* temp = NULL;

	if (pid == 0)
		pid = current_pid;

	if(multitasker[current_pid - 1].currheap + size <
								(multitasker[current_pid - 1].heap + PAGE_SIZE))
	{
		temp = (void*)multitasker[current_pid - 1].currheap;
		multitasker[current_pid - 1].currheap += size;
	}

	return temp;
}

int
task_to_fg(char * cmd)
{
	int tty, i, first = 1;

	tty = get_active_tty_num();

	for (i = multitasker[current_pid - 1].next_pid;
				i != multitasker[current_pid - 1].next_pid || first;
				i = multitasker[i - 1].next_pid)
	{
		first = 0;

		if ( multitasker[i - 1].tty == tty && multitasker[i - 1].is_bkg &&
			!strcmp( multitasker[i - 1].cmd, cmd))
		{
			multitasker[i - 1].is_bkg = 0;
			return multitasker[i - 1].pid;
		}
	}
	return 0;
}

int
task_alloc(void)
{
	unsigned int i;

	for (i = 0; i < TASKS_COUNT; i++)
		if (multitasker[i].status == KILLED)
		{
			multitasker[i].pid = (i + 1);
			multitasker[i].sp = STACK_PAGE_BEGIN + ((i+1) *
					STACK_PAGE_NEXT);
			multitasker[i].heap = STACK_PAGE_BEGIN + ((i+TASKS_COUNT) *
					STACK_PAGE_NEXT);
			multitasker[i].currheap = multitasker[i].heap;

			return (int) (i + 1);
		}

	return 0;
}


char *
get_pid_status(int i)
{
	return multitasker[i - 1].status == READY ? "READY" :
		multitasker[i - 1].status == BLOCKED ? "BLOCKED" :
		multitasker[i - 1].status == WAITCHILD ? "WAITCHILD" :
		multitasker[i - 1].status == RUNNING ? "RUNNING" : "SLEEPING";
}


int
task_exists(char *cmd, unsigned int tty)
{
	unsigned int i, first = 1;

	for (i = multitasker[current_pid - 1].next_pid;
			i != multitasker[current_pid - 1].next_pid || first;
			i = multitasker[i - 1].next_pid)
	{
		first = 0;
		if (strcmp(multitasker[i - 1].cmd, cmd) == 0 &&
				(multitasker[i - 1].tty == tty ||
				 tty == -1))
			return multitasker[i - 1].pid;
	}
	return 0;
}


static void
free_child_tasks(unsigned int ppid, unsigned int pid)
{
	unsigned int i, first = 1;

	for (i = current_pid; i != current_pid || first;
		       i = multitasker[i - 1].next_pid)
	{
		first = 0;
		if (multitasker[i - 1].ppid == ppid && i != pid )
			free_task(i, 0);
	}

}

void
set_schedule_priority(int priority)
{
	if(priority >= 0)
	scheduler_priority = priority;
}

int
get_schedule_priority(void)
{
	return scheduler_priority;
}

void
siginthandler()
{
	int tty, i, first = 1;

	tty = get_active_tty_num();


	for (i = multitasker[current_pid - 1].next_pid;
				i != multitasker[current_pid - 1].next_pid || first;
				i = multitasker[i - 1].next_pid)
	{
		first = 0;
		/* Libero la tarea que recibe el sigint si no es una consola */
		if ( multitasker[i - 1].tty == tty &&
			multitasker[i - 1].is_bkg == 0 &&
			(multitasker[i - 1].status == READY ||
			 multitasker[i - 1].status == RUNNING ||
			 multitasker[i - 1].status == SLEEPING)
			&& strcmp( multitasker[i - 1].cmd, "shell"))
			_free_task(multitasker[i - 1].pid, 0);

	}
	putchar('\n');
}

void
pageUp(int pid)
{
	/* Levanto la pagina del stack */
	page_table2[pid - 1] |= 0x00000001;

	/* Levanto la pagina del Heap */
	page_table2[(pid+TASKS_COUNT) - 1] |= 0x00000001;
}

void
pageDown(int pid)
{
	/* Se considera que el init siempre tiene pid = 1 y nunca se bajan las
	 * paginas correspondientes a su stack */
	if(pid == 1)
		return;

	/* Bajo la pagina del stack */
	page_table2[pid - 1] &= 0xFFFFFFFE;

	/* Bajo la pagina del Heap */
	page_table2[(pid+TASKS_COUNT) - 1] &= 0xFFFFFFFE;
}

void
print_page_info(void)
{
	int i, j;
	char in;
	char * dir;

	puts("Directorio de Paginas:\n");
	for( i=0; i < 3; i++)
		printf("%d: %x\n", i, (unsigned int)page_directory[i]);

	puts("\nQue tabla de paginas desea ver? [0 | 1 | 2] ");
	in= getchar();
	getchar();

	if( in == '0' )
	{
		puts("\n\n\nTabla de Paginas 0. Paginas (0-15):\n");
		for( i=0; i < 16; i++)
			printf("%d: %x\n", i, (unsigned int)page_table1[i]);

		puts("\nPRESIONE ENTER PARA CONTINUAR...");
		getchar();
		puts("\n");

		puts("\n\nPaginas (16-32):\n");
		for(; i < 32; i++)
			printf("%d: %x\n", i, (unsigned int)page_table1[i]);
	}
	else if( in == '1' )
	{
		puts("\n\n\nTabla de Paginas 1. Paginas (0-15):\n");
		for( i=0; i < 16; i++)
			printf("%d: %x\n", i, (unsigned int)page_table2[i]);

		puts("\nPRESIONE ENTER PARA CONTINUAR...");
		getchar();
		puts("\n");

		puts("\n\nPaginas (16-32):\n");
		for(; i < 32; i++)
			printf("%d: %x\n", i, (unsigned int)page_table2[i]);
	}
	else
	{
		puts("\n\n\nTabla de Paginas 2. Paginas (0-15):\n");
		for( i=0; i < 16; i++)
			printf("%d: %x\n", i, (unsigned int)page_table3[i]);

		puts("\nPRESIONE ENTER PARA CONTINUAR...");
		getchar();
		puts("\n");

		puts("\n\nPaginas (16-31):\n");
		for(; i < 32; i++)
			printf("%d: %x\n", i, (unsigned int)page_table3[i]);
	}
	puts("\nDesea probar generar un Page Fault? (Puede volver inestable "
			"al sistema) [s | n] ");

	in= getchar();
	getchar();

	if( in == 's' )
	{
		puts("\nGenerando Direccion de memoria random...\n");
		i= rand()%32;
		j= rand()%4096;
		printf("\n\nVoy a intentar escribir en la direccion: %p (Pagina %d)\n\n",
				(void *)page_table2[i]+j, i);
		dir= (void *)page_table2[i]+j;
		*dir= 0;

		puts("La pagina estaba habilitada! No hubo Page Fault!\n");
	}
	return;
}


